﻿using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition;
using ScrumFactory.Composition.ViewModel;
using ScrumFactory.Composition;
using ScrumFactory.Services;
using System.Windows.Input;
using System.Linq;



namespace ScrumFactory.Backlog.ViewModel {

    /// <summary>
    /// Backlog item Detail View Model.
    /// </summary> 
    public class BacklogItemDetailViewModel : BaseEditableObjectViewModel, IBacklogItemDetailViewModel, IEditableObjectViewModel, IPanelViewModel {

         
         private BacklogItem item;
         private IList<PlannedHour> originalPlannedHours;
         
         private IBacklogService backlogService;
         private IBackgroundExecutor executor;
         private IEventAggregator aggregator;
            
        /// <summary>
        /// Initializes a new instance of the <see cref="BacklogItemDetailViewModel"/> class.
        /// </summary>
        /// <param name="backlogService">The backlog service.</param>
        /// <param name="backgroundExecutor">The background executor.</param>
        /// <param name="eventAggregator">The event aggregator.</param>
        /// <param name="item">The item.</param>
        public BacklogItemDetailViewModel(
            IBacklogService backlogService,
            IBackgroundExecutor backgroundExecutor,
            IEventAggregator eventAggregator,
            BacklogItem item) {

            this.backlogService = backlogService;
            this.executor = backgroundExecutor;
            this.aggregator = eventAggregator;

            CloseWindowCommand = new DelegateCommand(CloseWindow);
            ShowDetailWindowCommand = new DelegateCommand(ShowDetailWindow);
            ReplanToCurrentSprintCommand = new DelegateCommand(ReplanToCurrentSprint);
            MoveItemToNextSprintCommand = new DelegateCommand(() => { MoveItemToSprintOffset(1); });
            MoveItemToPreviousSprintCommand = new DelegateCommand(() => { MoveItemToSprintOffset(-1); });
            

            Item = item;
        }

        /// <summary>
        /// Closes the window, invoke the OnCloseAction and publishes the CloseWindow event.
        /// </summary>
        private void CloseWindow() {
            executor.StartBackgroundTask(SaveBacklogItem, () => { });
            aggregator.Publish<IPanelViewModel>(ScrumFactoryEvent.CloseWindow, (IPanelViewModel)this);
            if (OnCloseAction != null)
                OnCloseAction.Invoke(this);                  
        }

        /// <summary>
        /// Saves the item, detecting if the planned hours were changed or not.
        /// </summary>
        private void SaveBacklogItem() {

            RefreshUI();
                          
            // if the planned hours was not changed, dont update it
            var changedHours = item.PlannedHours.Where(i => !originalPlannedHours.Any(o => o.RoleUId == i.RoleUId && o.Hours == i.Hours));
            if (changedHours.Count() > 0) {
                backlogService.UpdateBacklogItem(Item);                
            } else {
                backlogService.UpdateBacklogItemIgnoringHours(Item);
            }

            // clean the original values
            originalPlannedHours = null;
        }


        private void ReplanToCurrentSprint() {
            int offset = Item.Project.CurrentSprint.SprintNumber - (int)Item.SprintNumber;
            MoveItemToSprintOffset(offset);
        }

        #region IPanelViewModel Members

        /// <summary>
        /// Gets the panel display order when it is displayed at the view.
        /// </summary>
        /// <value>The panel display order.</value>
        public int PanelDisplayOrder {
            get {
                return 100;
            }
        }

        /// <summary>
        /// Gets the name of the panel.
        /// </summary>
        /// <value>The name of the panel.</value>
        public string PanelName {
            get {
                return Properties.Resources.Backlog_item;
            }
        }

        /// <summary>
        /// Gets the panel Unique id.
        /// </summary>
        /// <value>The panel Unique id.</value>
        public string PanelUId {
            get {
                return "BACKLOGITEM_DETAIL";
            }
        }

        /// <summary>
        /// Gets the view.
        /// </summary>
        /// <value>The view.</value>
        object IPanelViewModel.View {
            get {
                return this.View;
            }
        }


        public PanelPlacements PanelPlacement {
            get { return PanelPlacements.MainPanel; }
        }

        
        #endregion

        #region IBacklogItemDetailViewModel Members

        /// <summary>
        /// Gets or sets the view.
        /// </summary>
        /// <value>The view.</value>
        [Import()]
        public IBacklogItemDetailView View { get; set; }

        /// <summary>
        /// Refreshes the corresponding View Model view.
        /// </summary>
        public void RefreshUI() {
            OnPropertyChanged("Item");
            OnPropertyChanged("ItemTotalHoursInPixels");
            OnPropertyChanged("ItemSprintLabel");
            OnPropertyChanged("PlannedHours");
            OnPropertyChanged("ShowPlanningNumberLabel");
            OnPropertyChanged("CanMoveItemToPreviousSprint");
            OnPropertyChanged("CanMoveItemToNextSprint");
            OnPropertyChanged("CanEditHours");
        }


        /// <summary>
        /// Gets a value indicating whether this instance can edit hours.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can edit hours; otherwise, <c>false</c>.
        /// </value>
        public bool CanEditHours {
            get {
                return (item.SprintNumber >= item.Project.CurrentPlanningNumber);
            }
        }

        /// <summary>
        /// Gets the planned hours for this item.
        /// </summary>
        /// <value>The planned hours.</value>
        public ICollection<PlannedHour> PlannedHours {
            get {
                // no item, no candy
                if (item == null)
                    return null;
                // item has no hours planned, create it with zero
                if (item.PlannedHours == null || item.PlannedHours.Count == 0)
                    item.SyncPlannedHoursAndRoles();
                return item.PlannedHours;

            }
        }

        /// <summary>
        /// Gets the item.
        /// </summary>
        /// <value>The item.</value>
        public BacklogItem Item {
            get { return item; }
            set {
                item = value;
                RefreshUI();
            }
        }

        /// <summary>
        /// Gets or sets the item status.
        /// </summary>
        /// <value>The item status.</value>
        public short ItemStatus {
            get {
                return item.Status;
            }
            set {
                item.Status = value;
                OnPropertyChanged("Item");
            }
        }

        /// <summary>
        /// Gets the item sprint label.
        /// It could be the localized string for "Next Sprint", "Current Sprint", etc...
        /// </summary>
        /// <value>The item sprint label.</value>
        public string ItemSprintLabel {
            get {
                if (Item == null || Item.SprintNumber == null || Item.Project == null)
                    return null;

                Sprint currentSprint = Item.Project.CurrentSprint;

                if (currentSprint == null || !Item.Project.IsRunning)
                    return string.Format(Properties.Resources.sprint_N, Item.SprintNumber);

                if (currentSprint.SprintNumber == Item.SprintNumber)
                    return Properties.Resources.current_sprint;

                if (currentSprint.SprintNumber + 1 == Item.SprintNumber)
                    return Properties.Resources.next_sprint;

                if (currentSprint.SprintNumber - 1 == Item.SprintNumber)
                    return Properties.Resources.previous_sprint;

                return string.Format(Properties.Resources.sprint_N, Item.SprintNumber);
            }
        }

        /// <summary>
        /// Shows the detail window.
        /// </summary>
        private void ShowDetailWindow() {
            if (Item == null)
                return;
            originalPlannedHours = Item.PlannedHours.Clone();
            View = new BacklogItemDetail();
            View.Model = this;
            aggregator.Publish<object>(ScrumFactoryEvent.OpenNewWindow, this);
        }


        /// <summary>
        /// Moves the item to other sprint by a given offset.
        /// Use negative values to move to previous sprints.
        /// </summary>
        /// <param name="offset">The offset.</param>
        private void MoveItemToSprintOffset(int offset) {

            if (Item.Project == null)
                return;

            // border check
            int sourceSprintNumber = (int)Item.SprintNumber;
            int targetSprintNumber = (int)sourceSprintNumber + offset;
            if (targetSprintNumber < 1 || targetSprintNumber > Item.Project.Sprints.Count)
                return;

            // if the item is at an already finished sprint,
            // ignore offset, and move it to the current sprint
            if (sourceSprintNumber < Item.Project.CurrentValidSprint.SprintNumber)
                targetSprintNumber = Item.Project.CurrentValidSprint.SprintNumber;

            // move item
            Item.SprintNumber = targetSprintNumber;

            backlogService.UpdateBacklogItem(Item);

            RefreshUI();

            // notify other modules
            aggregator.Publish<object[]>(ScrumFactoryEvent.BacklogItemMoved, new object[] { item, sourceSprintNumber, targetSprintNumber });

        }


        /// <summary>
        /// Gets a value indicating whether this instance can move item to next sprint.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can move item to next sprint; otherwise, <c>false</c>.
        /// </value>
        public bool CanMoveItemToNextSprint {
            get {
                if (Item.Project == null || Item.Project.Sprints == null)
                    return false;
                return !Item.SprintNumber.Equals(Item.Project.Sprints.Count);
            }
        }

        /// <summary>
        /// Gets a value indicating whether this instance can move item to previous sprint.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance can move item to previous sprint; otherwise, <c>false</c>.
        /// </value>
        public bool CanMoveItemToPreviousSprint {
            get {
                if (Item.Project == null || Item.Project.Sprints == null)
                    return false;
                return Item.SprintNumber > Item.Project.CurrentValidSprint.SprintNumber;
            }
        }


        /// <summary>
        /// Gets the item total hours in pixels, so it can be displayied at the Sprint Panel.
        /// </summary>
        /// <value>The item total hours in pixels.</value>
        public double ItemTotalHoursInPixels {
            get {
                if (Item.CurrentTotalHours < 8)
                    return 16;
                if (Item.CurrentTotalHours > 90)
                    return 90 * 2;
                return (double)Item.CurrentTotalHours * 2;
            }
        }

        /// <summary>
        /// Gets or sets the on close action that is executed after a backlog item has been edited
        /// at the detail window.
        /// </summary>
        /// <value>The on close action.</value>
        public System.Action<IBacklogItemDetailViewModel> OnCloseAction { get; set; }

        /// <summary>
        /// Gets the close window command.
        /// </summary>
        /// <value>The close window command.</value>
        public ICommand CloseWindowCommand { get; private set; }

        /// <summary>
        /// Gets the show detail window command.
        /// </summary>
        /// <value>The show detail window command.</value>
        public ICommand ShowDetailWindowCommand { get; private set; }

        /// <summary>
        /// Gets the move item to next sprint command.
        /// </summary>
        /// <value>The move item to next sprint command.</value>
        public ICommand MoveItemToNextSprintCommand { private set; get; }

        /// <summary>
        /// Gets the move item to previous sprint command.
        /// </summary>
        /// <value>The move item to previous sprint command.</value>
        public ICommand MoveItemToPreviousSprintCommand { private set; get; }


        /// <summary>
        /// Gets or sets the replan to current sprint command.
        /// </summary>
        /// <value>The replan to current sprint command.</value>
        public ICommand ReplanToCurrentSprintCommand { get; set; }

        
        #endregion
    }
}
